/*
 ------------------------------------------------------------------------------
 Copyright (C) 2013 Eternal Games.

 This file is part of the EternalTech source code.

 The EternalTech source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The EternalTech source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the EternalTech source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// r_deform.cpp - surface deformations
//


#include "r_local.h"


/*
 ==================
 RB_DeformExpand
 ==================
*/
static void RB_DeformExpand (){

	glVertex_t	*vertices = backEnd.vertices;
	float		expand;
	int			i;

	rg.pc.deformExpand++;
	rg.pc.deformIndices += backEnd.numIndices;
	rg.pc.deformVertices += backEnd.numVertices;

	expand = backEnd.registers[backEnd.material->deformRegisters[0]];

	for (i = 0; i < backEnd.numVertices; i++)
		vertices[i].xyz += vertices[i].normal * expand;
}

/*
 ==================
 RB_DeformMove
 ==================
*/
static void RB_DeformMove (){

	glVertex_t	*vertices = backEnd.vertices;
	glqVec3		move;
	int			i;

	rg.pc.deformMove++;
	rg.pc.deformIndices += backEnd.numIndices;
	rg.pc.deformVertices += backEnd.numVertices;

	move[0] = backEnd.registers[backEnd.material->deformRegisters[0]];
	move[1] = backEnd.registers[backEnd.material->deformRegisters[1]];
	move[2] = backEnd.registers[backEnd.material->deformRegisters[2]];

	for (i = 0; i < backEnd.numVertices; i++)
		vertices[i].xyz += move;
}

/*
 ==================
 RB_DeformSprite
 ==================
*/
static void RB_DeformSprite (){

	glIndex_t	*indices = backEnd.indices;
	glVertex_t	*vertices = backEnd.vertices;
	glqVec3		lVector, uVector;
	glqVec3		center;
	float		radius;
	int			i;

	if (backEnd.numIndices != (backEnd.numVertices >> 2) * 6){
		Com_DPrintf(S_COLOR_YELLOW "Material '%s' has 'deform sprite' with an odd index count\n", backEnd.material->name);
		return;
	}
	if (backEnd.numVertices & 3){
		Com_DPrintf(S_COLOR_YELLOW "Material '%s' has 'deform sprite' with an odd vertex count\n", backEnd.material->name);
		return;
	}

	rg.pc.deformSprite++;
	rg.pc.deformIndices += backEnd.numIndices;
	rg.pc.deformVertices += backEnd.numVertices;

	// Assume all the triangles are independent quads
	for (i = 0; i < backEnd.numVertices; i += 4){
		// Compute center
		center = (vertices[0].xyz + vertices[1].xyz + vertices[2].xyz + vertices[3].xyz) * 0.25f;

		// Compute radius
		radius = center.DistanceFast(vertices->xyz) * M_SQRT_1OVER2;

		// Compute left and up vectors
		lVector = backEnd.localParms.viewAxis[1] * radius;
		uVector = backEnd.localParms.viewAxis[2] * radius;

		// Modify indices
		indices[0] = i;
		indices[1] = i + 1;
		indices[2] = i + 3;
		indices[3] = i + 3;
		indices[4] = i + 1;
		indices[5] = i + 2;

		indices += 6;

		// Modify vertices
		vertices[0].xyz = center + lVector + uVector;
		vertices[0].st.Set(0.0f, 0.0f);
		vertices[0].color.Identity();

		vertices[1].xyz = center - lVector + uVector;
		vertices[1].st.Set(1.0f, 0.0f);
		vertices[1].color.Identity();

		vertices[2].xyz = center - lVector - uVector;
		vertices[2].st.Set(1.0f, 1.0f);
		vertices[2].color.Identity();

		vertices[3].xyz = center + lVector - uVector;
		vertices[3].st.Set(0.0f, 1.0f);
		vertices[3].color.Identity();

		vertices += 4;
	}
}

/*
 ==================
 RB_DeformTube
 ==================
*/
static void RB_DeformTube (){

	static int	edgeIndex[6][2] = {{0, 1}, {1, 2}, {2, 0}, {3, 4}, {4, 5}, {5, 3}};
	glIndex_t	*indices = backEnd.indices;
	glVertex_t	*vertices = backEnd.vertices;
	glqVec3		dir, mid[2];
	float		len, lengths[2];
	int			edges[2];
	int			index0, index1, index2, index3;
	int			i, j;

	if (backEnd.numIndices != (backEnd.numVertices >> 2) * 6){
		Com_DPrintf(S_COLOR_YELLOW "Material '%s' has 'deform tube' with an odd index count\n", backEnd.material->name);
		return;
	}
	if (backEnd.numVertices & 3){
		Com_DPrintf(S_COLOR_YELLOW "Material '%s' has 'deform tube' with an odd vertex count\n", backEnd.material->name);
		return;
	}

	rg.pc.deformTube++;
	rg.pc.deformIndices += backEnd.numIndices;
	rg.pc.deformVertices += backEnd.numVertices;

	// Assume all the triangles are independent quads
	for (i = 0; i < backEnd.numVertices; i += 4){
		// Identify the two shortest edges
		edges[0] = edges[1] = 0;
		lengths[0] = lengths[1] = M_INFINITY;

		for (j = 0; j < 6; j++){
			len = (vertices[indices[edgeIndex[j][0]]].xyz - vertices[indices[edgeIndex[j][1]]].xyz).LengthSqr();

			if (len < lengths[0]){
				edges[1] = edges[0];
				lengths[1] = lengths[0];

				edges[0] = j;
				lengths[0] = len;
			}
			else if (len < lengths[1]){
				edges[1] = j;
				lengths[1] = len;
			}
		}

		// Get indices
		index0 = indices[edgeIndex[edges[0]][0]];
		index1 = indices[edgeIndex[edges[0]][1]];
		index2 = indices[edgeIndex[edges[1]][0]];
		index3 = indices[edgeIndex[edges[1]][1]];

		indices += 6;

		// Compute lengths
		lengths[0] = glqMath::SqrtFast(lengths[0]) * 0.5f;
		lengths[1] = glqMath::SqrtFast(lengths[1]) * 0.5f;

		// Compute mid points
		mid[0].Average(vertices[index0].xyz, vertices[index1].xyz);
		mid[1].Average(vertices[index2].xyz, vertices[index3].xyz);

		// Compute direction
		dir.Cross(mid[1] - mid[0], backEnd.localParms.viewAxis[0]);
		dir.NormalizeFast();

		// Modify vertices, leaving texture coords unchanged
		vertices[index0].xyz = mid[0] + dir * lengths[0];
		vertices[index0].color.Identity();

		vertices[index1].xyz = mid[0] - dir * lengths[0];
		vertices[index1].color.Identity();

		vertices[index2].xyz = mid[1] - dir * lengths[1];
		vertices[index2].color.Identity();

		vertices[index3].xyz = mid[1] + dir * lengths[1];
		vertices[index3].color.Identity();

		vertices += 4;
	}
}

/*
 ==================
 RB_DeformBeam
 ==================
*/
static void RB_DeformBeam (){

	static int	edgeIndex[6][2] = {{0, 1}, {1, 2}, {2, 0}, {3, 4}, {4, 5}, {5, 3}};
	glIndex_t	*indices = backEnd.indices;
	glVertex_t	*vertices = backEnd.vertices;
	glqVec3		dir, mid[2];
	float		len, lengths[2];
	int			edges[2];
	int			index0, index1, index2, index3;
	int			i, j;

	if (backEnd.numIndices != (backEnd.numVertices >> 2) * 6){
		Com_DPrintf(S_COLOR_YELLOW "Material '%s' has 'deform beam' with an odd index count\n", backEnd.material->name);
		return;
	}
	if (backEnd.numVertices & 3){
		Com_DPrintf(S_COLOR_YELLOW "Material '%s' has 'deform beam' with an odd vertex count\n", backEnd.material->name);
		return;
	}

	rg.pc.deformBeam++;
	rg.pc.deformIndices += backEnd.numIndices;
	rg.pc.deformVertices += backEnd.numVertices;

	// Assume all the triangles are independent quads
	for (i = 0; i < backEnd.numVertices; i += 4){
		// Identify the two shortest edges
		edges[0] = edges[1] = 0;
		lengths[0] = lengths[1] = M_INFINITY;

		for (j = 0; j < 6; j++){
			len = (vertices[indices[edgeIndex[j][0]]].xyz - vertices[indices[edgeIndex[j][1]]].xyz).LengthSqr();

			if (len < lengths[0]){
				edges[1] = edges[0];
				lengths[1] = lengths[0];

				edges[0] = j;
				lengths[0] = len;
			}
			else if (len < lengths[1]){
				edges[1] = j;
				lengths[1] = len;
			}
		}

		// Get indices
		index0 = indices[edgeIndex[edges[0]][0]];
		index1 = indices[edgeIndex[edges[0]][1]];
		index2 = indices[edgeIndex[edges[1]][0]];
		index3 = indices[edgeIndex[edges[1]][1]];

		indices += 6;

		// Compute lengths
		lengths[0] = glqMath::SqrtFast(lengths[0]) * 0.5f;
		lengths[1] = glqMath::SqrtFast(lengths[1]) * 0.5f;

		// Compute mid points
		mid[0].Average(vertices[index0].xyz, vertices[index1].xyz);
		mid[1].Average(vertices[index2].xyz, vertices[index3].xyz);

		// Compute direction
		dir.Cross(backEnd.localParms.viewOrigin - mid[0], mid[1] - mid[0]);
		dir.NormalizeFast();

		// Modify vertices, leaving texture coords unchanged
		vertices[index0].xyz = mid[0] + dir * lengths[0];
		vertices[index0].color.Identity();

		vertices[index1].xyz = mid[0] - dir * lengths[0];
		vertices[index1].color.Identity();

		vertices[index2].xyz = mid[1] - dir * lengths[1];
		vertices[index2].color.Identity();

		vertices[index3].xyz = mid[1] + dir * lengths[1];
		vertices[index3].color.Identity();

		vertices += 4;
	}
}

/*
 ==================
 RB_Deform
 ==================
*/
void RB_Deform (){

	if (r_skipDeforms->integerValue)
		return;

	switch (backEnd.material->deform){
	case DFRM_EXPAND:
		RB_DeformExpand();
		break;
	case DFRM_MOVE:
		RB_DeformMove();
		break;
	case DFRM_SPRITE:
		RB_DeformSprite();
		break;
	case DFRM_TUBE:
		RB_DeformTube();
		break;
	case DFRM_BEAM:
		RB_DeformBeam();
		break;
	}
}